home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue52 / HTML / Code / AppServer / usXMLDoc.pas < prev   
Encoding:
Pascal/Delphi Source File  |  1999-10-23  |  10.7 KB  |  430 lines

  1. unit usXMLDoc;
  2.  
  3. interface
  4.  
  5. uses
  6.   Classes, XmlParser {CueSoft};
  7.  
  8. type
  9.   TusXMLDocument = class;
  10.  
  11.   { TusXMLAttribute - a single tag attribute }
  12.   TusXMLAttribute = class(TPersistent)
  13.   private
  14.     FName: string;
  15.     FValue: string;
  16.   public
  17.     procedure Assign(aSource: TPersistent); override;
  18.     property Name: string read FName write FName;
  19.     property Value: string read FValue write FValue;
  20.   end;
  21.  
  22.   { TusXMLAttributes - a list of all attributes for a tag }
  23.   TusXMLAttributes = class(TPersistent)
  24.   protected
  25.     FList: TList;
  26.     function GetCount: Integer;
  27.     function GetItem(aIndex: Integer): TusXMLAttribute;
  28.   public
  29.     constructor Create;
  30.     destructor Destroy; override;
  31.     procedure Add(aItem: TusXMLAttribute);
  32.     procedure Assign(aSource: TPersistent); override;
  33.     procedure Clear;
  34.     function GetByName(aName: string): TusXMLAttribute;
  35.     function Value(aName: string): string;
  36.     property Count: Integer read GetCount;
  37.     property Items[aIndex: Integer]: TusXMLAttribute read GetItem; default;
  38.   end;
  39.  
  40.   { TusXMLElement - a single element (tag) }
  41.   TusXMLElement = class
  42.   private
  43.     FAttributes: TusXMLAttributes;
  44.     FData: string;
  45.     FLevel: SmallInt;
  46.     FParent: TusXMLElement;
  47.     FSubtags: TusXMLDocument;
  48.     FTagName: string;
  49.   public
  50.     constructor Create;
  51.     destructor Destroy; override;
  52.     property Attributes: TusXMLAttributes read FAttributes;
  53.     property Data: string read FData write FData;
  54.     property Level: SmallInt read FLevel write FLevel;
  55.     property Parent: TusXMLElement read FParent;
  56.     property Subtags: TusXMLDocument read FSubtags;
  57.     property TagName: string read FTagName write FTagName;
  58.   end;
  59.  
  60.   { TusXMLDocument - a contiguous block of XML tags }
  61.   TusXMLDocument = class
  62.   private
  63.   protected
  64.     FList: TList;
  65.     FRoot: TusXMLElement;
  66.     procedure AddElement(aElement: TusXMLElement);
  67.     function CreateNode(aParent: TusXMLElement; aTagName,
  68.       aData: string): TusXMLElement;
  69.     function GetCount: Integer;
  70.     function GetItem(aIndex: Integer): TusXMLElement;
  71.   public
  72.     constructor Create;
  73.     destructor Destroy; override;
  74.     function Add(aSibling: TusXMLElement; aName, aValue: string): TusXMLElement;
  75.     function AddChild(aParent: TusXMLElement; aName, aValue: string): TusXMLElement;
  76.     procedure Clear;
  77.     function GetFirstElement(aTagName: string): TusXMLElement;
  78.     property Count: Integer read GetCount;
  79.     property Items[aIndex: Integer]: TusXMLElement read GetItem; default;
  80.     property Root: TusXMLElement read FRoot;
  81.   end;
  82.  
  83.   { TusParser - parses raw XML and yields a TusXMLDocument structure }
  84.   TusXMLParser = class
  85.   private
  86.     FDocument: TusXMLDocument;
  87.  
  88.     { The following private declarations are specific to the third-party
  89.       parser being used to implement this class. }
  90.     Parser: TXMLParser;
  91.     NestingLevel: Integer;
  92.     LastElement: TusXMLElement;
  93.     Attributes: TusXMLAttributes;
  94.   protected
  95.     procedure DoOnAttribute(aSender: TObject; aName, aValue: string; aSpecified: Boolean);
  96.     procedure DoOnCDATASection(aSender: TObject; aValue: string);
  97.     procedure DoOnCharData(aSender: TObject; aValue: string);
  98.     procedure DoOnEndElement(aSender: TObject; aValue: string);
  99.     procedure DoOnStartDocument(aSender: TObject);
  100.     procedure DoOnStartElement(aSender: TObject; aValue: string);
  101.   public
  102.     constructor Create;
  103.     destructor Destroy; override;
  104.     procedure LoadXML(aXML: string); virtual;
  105.     property Document: TusXMLDocument read FDocument;
  106.   end;
  107.  
  108. implementation
  109.  
  110. uses
  111.   SysUtils;
  112.  
  113. { TusXMLAttribute }
  114.  
  115. procedure TusXMLAttribute.Assign(aSource: TPersistent);
  116. begin
  117.   if aSource is TusXMLAttribute then
  118.   begin
  119.     FName := TusXMLAttribute(aSource).FName;
  120.     FValue := TusXMLAttribute(aSource).FValue;
  121.   end
  122.   else
  123.     inherited Assign(aSource);
  124. end;
  125.  
  126. { TusXMLAttributes }
  127.  
  128. procedure TusXMLAttributes.Add(aItem: TusXMLAttribute);
  129. begin
  130.   FList.Add(aItem);
  131. end;
  132.  
  133. procedure TusXMLAttributes.Assign(aSource: TPersistent);
  134. var
  135.   I: Integer;
  136. begin
  137.   if aSource is TusXMLAttributes then
  138.   begin
  139.     Clear;
  140.     for I := 0 to TusXMLAttributes(aSource).Count - 1 do
  141.     begin
  142.       Add(TusXMLAttribute.Create);
  143.       Items[Count - 1].Assign(TusXMLAttributes(aSource)[I]);
  144.     end;
  145.   end
  146.   else
  147.     inherited Assign(aSource);
  148. end;
  149.  
  150. procedure TusXMLAttributes.Clear;
  151. var
  152.   I: Integer;
  153. begin
  154.   for I := 0 to Count - 1 do
  155.     Items[I].Free;
  156.   FList.Clear;
  157. end;
  158.  
  159. constructor TusXMLAttributes.Create;
  160. begin
  161.   inherited;
  162.   FList := TList.Create;
  163. end;
  164.  
  165. destructor TusXMLAttributes.Destroy;
  166. begin
  167.   Clear;
  168.   FList.Free;
  169.   inherited;
  170. end;
  171.  
  172. function TusXMLAttributes.GetByName(aName: string): TusXMLAttribute;
  173. var
  174.   I: Integer;
  175. begin
  176.   Result := nil;
  177.   for I := 0 to Count - 1 do
  178.     if CompareText(aName, Items[I].Name) = 0 then
  179.     begin
  180.       Result := Items[I];
  181.       Break;
  182.     end;
  183. end;
  184.  
  185. function TusXMLAttributes.GetCount: Integer;
  186. begin
  187.   Result := FList.Count;
  188. end;
  189.  
  190. function TusXMLAttributes.GetItem(aIndex: Integer): TusXMLAttribute;
  191. begin
  192.   Result := TusXMLAttribute(FList[aIndex]);
  193. end;
  194.  
  195. function TusXMLAttributes.Value(aName: string): string;
  196. var
  197.   Attr: TusXMLAttribute;
  198. begin
  199.   Result := '';
  200.   Attr := GetByName(aName);
  201.   if Assigned(Attr) then
  202.     Result := Attr.Value;
  203. end;
  204.  
  205. { TusXMLElement }
  206.  
  207. constructor TusXMLElement.Create;
  208. begin
  209.   inherited;
  210.   FAttributes := TusXMLAttributes.Create;
  211.   FSubtags := TusXMLDocument.Create;
  212. end;
  213.  
  214. destructor TusXMLElement.Destroy;
  215. begin
  216.   FAttributes.Free;
  217.   FSubtags.Free;
  218.   inherited;
  219. end;
  220.  
  221. { TusXMLDocument }
  222.  
  223. function TusXMLDocument.Add(aSibling: TusXMLElement; aName,
  224.   aValue: string): TusXMLElement;
  225. { Add a new XML element to the list }
  226. begin
  227.   if not Assigned(aSibling) then
  228.   begin
  229.     Result := CreateNode(nil, aName, aValue);
  230.     AddElement(Result);
  231.   end
  232.   else
  233.   begin
  234.     Result := CreateNode(aSibling.Parent, aName, aValue);
  235.     Result.Level := aSibling.Parent.Level + 1;
  236.     aSibling.Parent.Subtags.AddElement(Result);
  237.   end;
  238. end;
  239.  
  240. function TusXMLDocument.AddChild(aParent: TusXMLElement; aName,
  241.   aValue: string): TusXMLElement;
  242. begin
  243.   Assert(Assigned(aParent), 'Parent element not assigned.');
  244.  
  245.   Result := CreateNode(aParent, aName, aValue);
  246.   Result.Level := aParent.Level + 1;
  247.   aParent.Subtags.AddElement(Result);
  248. end;
  249.  
  250. procedure TusXMLDocument.AddElement(aElement: TusXMLElement);
  251. begin
  252.   FList.Add(aElement);
  253.   if not Assigned(FRoot) then
  254.     FRoot := aElement;
  255. end;
  256.  
  257. procedure TusXMLDocument.Clear;
  258. var
  259.   I: Integer;
  260. begin
  261.   for I := 0 to Count - 1 do
  262.     TusXMLElement(FList[I]).Free;
  263.   FList.Clear;
  264.   FRoot := nil;  
  265.   inherited;
  266. end;
  267.  
  268. constructor TusXMLDocument.Create;
  269. begin
  270.   inherited;
  271.   FList := TList.Create;
  272. end;
  273.  
  274. function TusXMLDocument.CreateNode(aParent: TusXMLElement; aTagName, aData: string): TusXMLElement;
  275. { If aParent is unassigned, then we are added a zero-level node }
  276. begin
  277.   Result := TusXMLElement.Create;
  278.   Result.TagName := AnsiUpperCase(aTagName);
  279.   Result.Data := aData;
  280.   Result.FParent := aParent;
  281. end;
  282.  
  283. destructor TusXMLDocument.Destroy;
  284. begin
  285.   Clear;
  286.   FList.Free;
  287.   inherited;
  288. end;
  289.  
  290. function TusXMLDocument.GetCount: Integer;
  291. begin
  292.   Result := FList.Count;
  293. end;
  294.  
  295. function TusXMLDocument.GetFirstElement(aTagName: string): TusXMLElement;
  296. var
  297.   I: Integer;
  298. begin
  299.   Result := nil;
  300.   for I := 0 to Root.Subtags.Count - 1 do
  301.     if CompareText(aTagName, Root.Subtags[I].TagName) = 0 then
  302.     begin
  303.       Result := Items[I];
  304.       Exit;
  305.     end;
  306. end;
  307.  
  308. function TusXMLDocument.GetItem(aIndex: Integer): TusXMLElement;
  309. begin
  310.   Result := TusXMLElement(FList[aIndex]);
  311. end;
  312.  
  313. { TusXMLParser }
  314.  
  315. constructor TusXMLParser.Create;
  316. begin
  317.   inherited;
  318.   Parser := TXMLParser.Create(nil);
  319.   with Parser do
  320.   begin
  321.     NormalizeData := True;
  322.     OnStartDocument := DoOnStartDocument;
  323.     OnAttribute := DoOnAttribute;
  324.     OnStartElement := DoOnStartElement;
  325.     OnCDATASection := DoOnCDATASection;
  326.     OnCharData := DoOnCharData;
  327.     OnEndElement := DoOnEndElement;
  328.   end;
  329.   Attributes := TusXMLAttributes.Create;
  330.   FDocument := TusXMLDocument.Create;
  331. end;
  332.  
  333. destructor TusXMLParser.Destroy;
  334. begin
  335.   Attributes.Free;
  336.   FDocument.Free;
  337.   Parser.Free;
  338.   inherited;
  339. end;
  340.  
  341. procedure TusXMLParser.DoOnAttribute(aSender: TObject; aName,
  342.   aValue: string; aSpecified: Boolean);
  343. { OnAttribute is fired BEFORE the OnStartElement for the tag containing
  344.   the attributes.  So we must accumulate the attributes and wait for the
  345.   OnStartElement event. }
  346. var
  347.   A: TusXMLAttribute;
  348. begin
  349.   A := TusXMLAttribute.Create;
  350.   A.Name := ANSILowercase(aName);
  351.   A.Value := aValue;
  352.   Attributes.Add(A);
  353. end;
  354.  
  355. procedure TusXMLParser.DoOnCDATASection(aSender: TObject; aValue: string);
  356. begin
  357.   with LastElement do
  358.   begin
  359.     Data := Data + aValue;
  360.   end;
  361. end;
  362.  
  363. procedure TusXMLParser.DoOnCharData(aSender: TObject; aValue: string);
  364. begin
  365.   with LastElement do
  366.   begin
  367.     Data := Data + aValue;
  368.   end;
  369. end;
  370.  
  371. procedure TusXMLParser.DoOnEndElement(aSender: TObject; aValue: string);
  372. begin
  373.   Dec(NestingLevel);
  374. end;
  375.  
  376. procedure TusXMLParser.DoOnStartDocument(aSender: TObject);
  377. begin
  378.   LastElement := nil;
  379.   NestingLevel := -1;
  380.   Attributes.Clear;
  381. end;
  382.  
  383. procedure TusXMLParser.DoOnStartElement(aSender: TObject; aValue: string);
  384. { On entry: LastElement refers to the last element we created or nil if
  385.   this is the first element.
  386.   We create a new element and LastElement now points to the new element. }
  387. var
  388.   ParentElement: TusXMLElement;
  389.   I: Integer;
  390. begin
  391.   Inc(NestingLevel);
  392.   if not Assigned(LastElement) or (NestingLevel = LastElement.Level) then
  393.     { root element (XML tag), or new sibling of previous element }
  394.     LastElement := FDocument.Add(LastElement, aValue, '')
  395.   else
  396.     if NestingLevel > LastElement.Level then
  397.       { first child of previous element }
  398.       LastElement := FDocument.AddChild(LastElement, aValue, '')
  399.     else
  400.     begin
  401.       { next sibling of previous element's parent }
  402.       ParentElement := LastElement;
  403.       for I := LastElement.Level - 1 downto NestingLevel do
  404.         ParentElement := ParentElement.Parent;
  405.       LastElement := FDocument.Add(ParentElement, aValue, '');
  406.     end;
  407.  
  408.   { Copy attributes }
  409.   LastElement.Attributes.Assign(Attributes);
  410.   Attributes.Clear;
  411. end;
  412.  
  413. procedure TusXMLParser.LoadXML(aXML: string);
  414. var
  415.   ErrorMsg: string;
  416.   I: Integer;
  417. begin
  418.   Document.Clear;
  419.   if not Parser.ParseMemory(PChar(aXML)) then
  420.     with Parser.Errors do
  421.     begin
  422.       ErrorMsg := 'Error parsing UWML:';
  423.       for I := 0 to Count - 1 do
  424.         ErrorMsg := ErrorMsg + #13#10 + Strings[I];
  425.       raise Exception.Create(ErrorMsg);
  426.     end;
  427. end;
  428.  
  429. end.
  430.